﻿using Core.Entities;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace Core.Tools
{
    public static class MethodHelper
    {
        private static int TypeId = 1;

        private static readonly Dictionary<MethodSign, Type> DelegateTable = new Dictionary<MethodSign, Type>();
        private static readonly object DelegateTableLock = new object();
        private static readonly AssemblyBuilder AssBuilder;
        private static readonly ModuleBuilder ModBuilder;

        private static void FlagMethod(Delegate Instance, object Target, IntPtr FunctionPointer)
        {

        }

        static MethodHelper()
        {
            AssemblyName AssName = new AssemblyName(
                "MethodHelper"
                );

            AssBuilder =
                AppDomain.CurrentDomain.DefineDynamicAssembly(
                    AssName,
                    AssemblyBuilderAccess.Run);

            ModBuilder = AssBuilder.DefineDynamicModule(
                "MethodHelper");
        }

        public static Type GetDelegateType(Type[] ParamsTypes, Type ResultType)
        {
            string DelegateName = ResultType == typeof(void) ? "Action" : "Func";

            var SignKey = new MethodSign(DelegateName, ParamsTypes, ResultType);

            Type Result;

            if (!DelegateTable.TryGetValue(SignKey, out Result))
            {
                lock (DelegateTableLock)
                {
                    if (!DelegateTable.TryGetValue(SignKey, out Result))
                    {
                        var DynamicDelegateType = DefineDelegate(ModBuilder, DelegateName + "_" + TypeId, ParamsTypes, ResultType, TypeAttributes.Public, true);

                        ++TypeId;

                        Result = DynamicDelegateType.CreateType();

                        DelegateTable.Add(SignKey, Result);
                    }
                }
            }

            return Result;
        }

        /// <summary>
        /// 定义动态委托
        /// </summary>
        /// <param name="ModBuilder">需要定义委托的模块</param>
        /// <param name="DelegateName">委托的名称</param>
        /// <param name="ParamsTypes">委托的参数类型列表</param>
        /// <param name="ResultType">委托的返回值类型</param>
        /// <param name="Attributes">委托的属性</param>
        /// <returns>返回一个未生成的动态委托类型</returns>
        public unsafe static TypeBuilder DefineDelegate(ModuleBuilder ModBuilder, string DelegateName, Type[] ParamsTypes, Type ResultType, TypeAttributes Attributes = TypeAttributes.Public, bool ImplIDelegate = false)
        {
            var DelegateBuilder = ModBuilder.DefineType(
                DelegateName,
                TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.Sealed | Attributes, // AutoLayout | AnsiClass | Sealed　是必须的
                typeof(MulticastDelegate)); // 必须继承 MulticastDelegate 类

            var CtorBuilder = DelegateBuilder.DefineConstructor(
                MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, // IL 代码中是这样标识的
                CallingConventions.Standard,
                new[] { typeof(object), typeof(IntPtr) } // 构造函数参数固定为 (object, IntPtr)
                );
            CtorBuilder.SetImplementationFlags(MethodImplAttributes.Runtime);// 添加 runtime managed 标识

            var InvokeMethodBuilder = DelegateBuilder.DefineMethod(
                "Invoke",
                MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, // IL 代码中是这样标识的
                CallingConventions.Standard,
                ResultType, // 返回值类型
                ParamsTypes); // 参数类型列表
            InvokeMethodBuilder.SetImplementationFlags(MethodImplAttributes.Runtime); // 添加 runtime managed 标识

            #region -- 重写 DynamicInvokeImpl 方法，使动态调用效率更高。

            var DynamicInvokeMethodBuilder = DelegateBuilder.DefineMethod(
                "DynamicInvokeImpl",
                MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual,
                CallingConventions.Standard,
                typeof(object),
                new Type[] { typeof(object[]) });

            if (ImplIDelegate)
            {
                DelegateBuilder.DefineMethodOverride(DynamicInvokeMethodBuilder, typeof(IDelegate).GetMethod("DynamicInvoke", new Type[] { typeof(object[]) }));
            }

            var ILGenerator = DynamicInvokeMethodBuilder.GetILGenerator();

            ILGenerator.Emit_Ldarg(0);

            for (int i = 0; i < ParamsTypes.Length; i++)
            {
                ILGenerator.Emit_Ldarg(1);
                ILGenerator.Emit_Ldc_I4(i);

                if (ParamsTypes[i].IsByRef)
                {
                    var ParamType = ParamsTypes[i].GetElementType();

                    if (ParamType.IsValueType)
                    {
                        ILGenerator.Emit(OpCodes.Ldelem_Ref);
                        ILGenerator.Emit_Ldc_I4(sizeof(IntPtr));
                        ILGenerator.Emit(OpCodes.Add);
                    }
                    else
                    {
                        ILGenerator.Emit(OpCodes.Ldelema, typeof(object));
                    }
                }
                else
                {
                    ILGenerator.Emit(OpCodes.Ldelem_Ref);
                    ILGenerator.Emit_Cast(ParamsTypes[i]);
                }
            }

            ILGenerator.Emit(OpCodes.Call, InvokeMethodBuilder);

            if (ResultType == typeof(void))
            {
                ILGenerator.Emit(OpCodes.Ldnull);
            }
            else if (ResultType.IsValueType)
            {
                ILGenerator.Emit(OpCodes.Box, ResultType);
            }

            ILGenerator.Emit(OpCodes.Ret);

            #endregion

            if (ImplIDelegate)
            {
                ImplementationIDelegate(DelegateBuilder, ParamsTypes, ResultType, InvokeMethodBuilder);
            }

            return DelegateBuilder;
        }

        private unsafe static void ImplementationIDelegate(TypeBuilder DelegateBuilder, Type[] ParamsTypes, Type ResultType, MethodBuilder InvokeMethodBuilder)
        {
            DelegateBuilder.AddInterfaceImplementation(typeof(IDelegate));

            #region -- DynamicInvoke(IntPtr[] Params, IntPtr Result)。

            var DynamicInvokeMethodBuilder = DelegateBuilder.DefineMethod(
                "DynamicInvoke",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.NewSlot | MethodAttributes.Virtual |
                MethodAttributes.Final,
                CallingConventions.HasThis,
                typeof(void),
                new Type[] { typeof(IntPtr[]), typeof(IntPtr) });

            var DynamicInvokeILGenerator = DynamicInvokeMethodBuilder.GetILGenerator();

            if (ResultType != typeof(void))
            {
                DynamicInvokeILGenerator.Emit_Ldarg(2);
            }

            DynamicInvokeILGenerator.Emit_Ldarg(0);

            for (int i = 0; i < ParamsTypes.Length; i++)
            {
                DynamicInvokeILGenerator.Emit_Ldarg(1);
                DynamicInvokeILGenerator.Emit_Ldc_I4(i);

                if (ParamsTypes[i].IsByRef)
                {
                    DynamicInvokeILGenerator.Emit(OpCodes.Ldelem_I);
                }
                else if (ParamsTypes[i].IsValueType)
                {
                    DynamicInvokeILGenerator.Emit(OpCodes.Ldelem_I);
                    DynamicInvokeILGenerator.Emit(OpCodes.Ldobj, ParamsTypes[i]);
                }
                else
                {
                    DynamicInvokeILGenerator.Emit(OpCodes.Ldelem_I);
                }
            }

            DynamicInvokeILGenerator.Emit(OpCodes.Call, InvokeMethodBuilder);

            if (ResultType != typeof(void))
            {
                DynamicInvokeILGenerator.Emit_Stobj(ResultType);
            }

            DynamicInvokeILGenerator.Emit(OpCodes.Ret);

            #endregion


            #region -- DynamicInvoke(object Intance, object[] Params)。

            var DynamicInvoke2MethodBuilder = DelegateBuilder.DefineMethod(
                "DynamicInvoke",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.NewSlot | MethodAttributes.Virtual |
                MethodAttributes.Final,
                CallingConventions.HasThis,
                typeof(object),
                new Type[] { typeof(object), typeof(object[]) });

            var DynamicInvoke2ILGenerator = DynamicInvoke2MethodBuilder.GetILGenerator();

            DynamicInvoke2ILGenerator.Emit_Ldarg(0);

            if (ParamsTypes.Length >= 1)
            {
                var ParamType = ParamsTypes[0];

                if (ParamType.IsByRef)
                {
                    ParamType = ParamType.GetElementType();

                    if (ParamType.IsValueType)
                    {
                        DynamicInvoke2ILGenerator.Emit_Ldarg(1);
                        DynamicInvoke2ILGenerator.Emit_Ldc_I4(sizeof(IntPtr));
                        DynamicInvoke2ILGenerator.Emit(OpCodes.Add);
                    }
                    else
                    {
                        DynamicInvoke2ILGenerator.Emit_Ldarga(1);
                    }
                }
                else
                {
                    DynamicInvoke2ILGenerator.Emit_Ldarg(1);
                }

                for (int i = 1; i < ParamsTypes.Length; i++)
                {
                    DynamicInvoke2ILGenerator.Emit_Ldarg(2);
                    DynamicInvoke2ILGenerator.Emit_Ldc_I4(i - 1);

                    ParamType = ParamsTypes[i];

                    if (ParamType.IsByRef)
                    {
                        ParamType = ParamType.GetElementType();

                        if (ParamType.IsValueType)
                        {
                            DynamicInvoke2ILGenerator.Emit(OpCodes.Ldelem_Ref);
                            DynamicInvoke2ILGenerator.Emit_Ldc_I4(sizeof(IntPtr));
                            DynamicInvoke2ILGenerator.Emit(OpCodes.Add);
                        }
                        else
                        {
                            DynamicInvoke2ILGenerator.Emit(OpCodes.Ldelema, typeof(object));
                        }
                    }
                    else
                    {
                        DynamicInvoke2ILGenerator.Emit(OpCodes.Ldelem_Ref);
                        DynamicInvoke2ILGenerator.Emit_Cast(ParamType);
                    }
                }
            }

            DynamicInvoke2ILGenerator.Emit(OpCodes.Call, InvokeMethodBuilder);

            if (ResultType == typeof(void))
            {
                DynamicInvoke2ILGenerator.Emit(OpCodes.Ldnull);
            }
            else if (ResultType.IsValueType)
            {
                DynamicInvoke2ILGenerator.Emit(OpCodes.Box, ResultType);
            }

            DynamicInvoke2ILGenerator.Emit(OpCodes.Ret);

            #endregion


            #region -- DynamicInvoke(object Intance, object[] Params, object Value)。

            var DynamicInvoke4MethodBuilder = DelegateBuilder.DefineMethod(
                "DynamicInvoke",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.NewSlot | MethodAttributes.Virtual |
                MethodAttributes.Final,
                CallingConventions.HasThis,
                typeof(object),
                new Type[] { typeof(object), typeof(object[]), typeof(object) });

            var DynamicInvoke4ILGenerator = DynamicInvoke4MethodBuilder.GetILGenerator();

            DynamicInvoke4ILGenerator.Emit_Ldarg(0);

            if (ParamsTypes.Length >= 1)
            {
                var ParamType = ParamsTypes[0];

                if (ParamType.IsByRef)
                {
                    ParamType = ParamType.GetElementType();

                    if (ParamType.IsValueType)
                    {
                        DynamicInvoke4ILGenerator.Emit_Ldarg(1);
                        DynamicInvoke4ILGenerator.Emit_Ldc_I4(sizeof(IntPtr));
                        DynamicInvoke4ILGenerator.Emit(OpCodes.Add);
                    }
                    else
                    {
                        DynamicInvoke4ILGenerator.Emit_Ldarga(1);
                    }
                }
                else
                {
                    DynamicInvoke4ILGenerator.Emit_Ldarg(1);
                }

                if (ParamsTypes.Length >= 2)
                {
                    var Right = ParamsTypes.Length - 1;

                    for (int i = 1; i < Right; i++)
                    {
                        DynamicInvoke4ILGenerator.Emit_Ldarg(2);
                        DynamicInvoke4ILGenerator.Emit_Ldc_I4(i - 1);

                        ParamType = ParamsTypes[i];

                        if (ParamType.IsByRef)
                        {
                            ParamType = ParamType.GetElementType();

                            if (ParamType.IsValueType)
                            {
                                DynamicInvoke4ILGenerator.Emit(OpCodes.Ldelem_Ref);
                                DynamicInvoke4ILGenerator.Emit_Ldc_I4(sizeof(IntPtr));
                                DynamicInvoke4ILGenerator.Emit(OpCodes.Add);
                            }
                            else
                            {
                                DynamicInvoke4ILGenerator.Emit(OpCodes.Ldelema, typeof(object));
                            }
                        }
                        else
                        {
                            DynamicInvoke4ILGenerator.Emit(OpCodes.Ldelem_Ref);
                            DynamicInvoke4ILGenerator.Emit_Cast(ParamType);
                        }
                    }

                    ParamType = ParamsTypes[Right];

                    if (ParamType.IsByRef)
                    {
                        ParamType = ParamType.GetElementType();

                        if (ParamType.IsValueType)
                        {
                            DynamicInvoke4ILGenerator.Emit_Ldarg(3);
                            DynamicInvoke4ILGenerator.Emit_Ldc_I4(sizeof(IntPtr));
                            DynamicInvoke4ILGenerator.Emit(OpCodes.Add);
                        }
                        else
                        {
                            DynamicInvoke4ILGenerator.Emit_Ldarga(3);
                        }
                    }
                    else
                    {
                        DynamicInvoke4ILGenerator.Emit_Ldarg(3);

                        if (ParamType.IsValueType)
                        {
                            DynamicInvoke4ILGenerator.Emit(OpCodes.Unbox_Any, ParamType);
                        }
                    }
                }

            }

            DynamicInvoke4ILGenerator.Emit(OpCodes.Call, InvokeMethodBuilder);

            if (ResultType == typeof(void))
            {
                DynamicInvoke4ILGenerator.Emit(OpCodes.Ldnull);
            }
            else if (ResultType.IsValueType)
            {
                DynamicInvoke4ILGenerator.Emit(OpCodes.Box, ResultType);
            }

            DynamicInvoke4ILGenerator.Emit(OpCodes.Ret);

            #endregion


            #region -- DynamicInvoke(TypedReference TypedRef, IntPtr[] Params)。

            var DynamicInvoke3MethodBuilder = DelegateBuilder.DefineMethod(
                "DynamicInvoke",
                MethodAttributes.Public | MethodAttributes.HideBySig |
                MethodAttributes.NewSlot | MethodAttributes.Virtual |
                MethodAttributes.Final,
                CallingConventions.HasThis,
                typeof(object),
                new Type[] { typeof(TypedReference), typeof(object[]) });

            var DynamicInvoke3ILGenerator = DynamicInvoke3MethodBuilder.GetILGenerator();

            DynamicInvoke3ILGenerator.Emit_Ldarg(0);

            if (ParamsTypes.Length >= 1)
            {
                var ParamType = ParamsTypes[0];

                DynamicInvoke3ILGenerator.Emit_Ldarga(1);
                DynamicInvoke3ILGenerator.Emit(OpCodes.Ldind_I);

                if (!ParamType.IsByRef)
                {
                    DynamicInvoke3ILGenerator.Emit(OpCodes.Ldind_I);
                }

                for (int i = 1; i < ParamsTypes.Length; i++)
                {
                    DynamicInvoke3ILGenerator.Emit_Ldarg(2);
                    DynamicInvoke3ILGenerator.Emit_Ldc_I4(i - 1);

                    ParamType = ParamsTypes[i];

                    if (ParamType.IsByRef)
                    {
                        ParamType = ParamType.GetElementType();

                        if (ParamType.IsValueType)
                        {
                            DynamicInvoke3ILGenerator.Emit(OpCodes.Ldelem_Ref);
                            DynamicInvoke3ILGenerator.Emit_Ldc_I4(sizeof(IntPtr));
                            DynamicInvoke3ILGenerator.Emit(OpCodes.Add);
                        }
                        else
                        {
                            DynamicInvoke3ILGenerator.Emit(OpCodes.Ldelema, typeof(object));
                        }
                    }
                    else
                    {
                        DynamicInvoke3ILGenerator.Emit(OpCodes.Ldelem_Ref);
                        DynamicInvoke3ILGenerator.Emit_Cast(ParamType);
                    }
                }
            }

            DynamicInvoke3ILGenerator.Emit(OpCodes.Call, InvokeMethodBuilder);

            if (ResultType == typeof(void))
            {
                DynamicInvoke3ILGenerator.Emit(OpCodes.Ldnull);
            }
            else if (ResultType.IsValueType)
            {
                DynamicInvoke3ILGenerator.Emit(OpCodes.Box, ResultType);
            }

            DynamicInvoke3ILGenerator.Emit(OpCodes.Ret);

            #endregion


        }

        public static IDelegate CreateDelegate(MethodInfo Method)
        {
            if (Method == null)
            {
                throw new NullReferenceException("Method");
            }

            Type[] ParamsTypes;
            Type ResultType;

            GetParamsTypes(Method, out ParamsTypes, out ResultType);

            Type Type = GetDelegateType(ParamsTypes, ResultType);

            return (IDelegate)Delegate.CreateDelegate(Type, Method);
        }

        public static IDelegate CreateDelegate(MethodBase Method)
        {
            if (Method == null)
            {
                throw new NullReferenceException("Method");
            }

            Type[] ParamsTypes;
            Type ResultType;

            GetParamsTypes(Method, out ParamsTypes, out ResultType);

            Type Type = GetDelegateType(ParamsTypes, ResultType);

            return (IDelegate)CreateDelegate(Type, Method.MethodHandle.GetFunctionPointer());
        }

        public unsafe static Delegate CreateDelegate(Type DelegateType, IntPtr FunctionPointer)
        {
            if (!typeof(Delegate).IsAssignableFrom(DelegateType))
            {
                throw new ArgumentException("Must be a delegate type.");
            }

            var Result = (Delegate)TypeHelper.Allocate(DelegateType);

            var DelegateCtorDelegate = new Action<Delegate, object, IntPtr>(FlagMethod);

            var LpDelegateCtorDelegate = (DelegateStruct*)Pointer.UnBox(DelegateCtorDelegate);

            LpDelegateCtorDelegate->_methodPtrAux = DelegateType.GetConstructor(new Type[] { typeof(object), typeof(IntPtr) }).MethodHandle.GetFunctionPointer();

            DelegateCtorDelegate(Result, null, FunctionPointer);

            return Result;
        }

        public static void GetParamsTypes(MethodBase MethodInfo, out Type[] ParamsTypes, out Type ResultType)
        {
            var ParamsInfo = MethodInfo.GetParameters();

            int Offset = 0;

            if (!MethodInfo.IsStatic)
            {
                ParamsTypes = new Type[ParamsInfo.Length + 1];

                if (MethodInfo.DeclaringType.IsValueType)
                {
                    ParamsTypes[0] = MethodInfo.DeclaringType.MakeByRefType();
                }
                else
                {
                    ParamsTypes[0] = MethodInfo.DeclaringType;
                }

                Offset = 1;
            }
            else
            {
                ParamsTypes = new Type[ParamsInfo.Length];
            }

            foreach (var Item in ParamsInfo)
            {
                ParamsTypes[Offset] = Item.ParameterType;

                ++Offset;
            }

            if (MethodInfo is MethodInfo)
            {
                ResultType = ((MethodInfo)MethodInfo).ReturnType;
            }
            else
            {
                ResultType = typeof(void);
            }
        }
    }

    internal struct DelegateStruct
    {
        public IntPtr TypeHandle;
        public IntPtr _target;
        public IntPtr _methodBase;
        public IntPtr _methodPtr;
        public IntPtr _methodPtrAux;

        public unsafe DelegateStruct(Delegate InternalDelegate)
        {
            var LpInternalDelegate = (DelegateStruct*)Pointer.UnBox(InternalDelegate);

            TypeHandle = LpInternalDelegate->TypeHandle;
            _target = LpInternalDelegate->_target;
            _methodBase = LpInternalDelegate->_methodBase;
            _methodPtr = LpInternalDelegate->_methodPtr;
            _methodPtrAux = LpInternalDelegate->_methodPtrAux;
        }
    }

    public interface IDelegate
    {
        object DynamicInvoke(object Instance, object[] Params, object Value);

        object DynamicInvoke(object Instance, object[] Params);

        object DynamicInvoke(object[] Params);

        object DynamicInvoke(TypedReference TypedRef, object[] Params);

        void DynamicInvoke(IntPtr[] Params, IntPtr Result);
    }
}